package org.fast.develop.logicflow.runtime.executor;

import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.fast.develop.logicflow.core.meta.base.Element;
import org.fast.develop.logicflow.core.meta.base.Resource;
import org.fast.develop.logicflow.core.meta.condition.ConditionExpression;
import org.fast.develop.logicflow.core.meta.shape.control.IfElseControl;
import org.fast.develop.logicflow.core.meta.shape.line.SequenceFlow;
import org.fast.develop.logicflow.runtime.cache.IThreadStore;
import org.fast.develop.logicflow.runtime.cache.ThreadLog;
import org.fast.develop.logicflow.runtime.context.LogicFlowContext;
import org.fast.develop.logicflow.runtime.context.LogicFlowContextManager;
import org.fast.develop.logicflow.runtime.debug.ExecuteDebugConfig;
import org.fast.develop.logicflow.runtime.enums.ExecuteDebugEnum;
import org.fast.develop.logicflow.runtime.enums.WebsocketStatusEnum;
import org.fast.develop.logicflow.runtime.exception.LogicFlowException;
import org.fast.develop.logicflow.runtime.exception.LogicFlowRuntimeException;
import org.fast.develop.logicflow.runtime.expression.DefaultJuelExpressionActuator;
import org.fast.develop.logicflow.runtime.instance.LogicFlowInstance;
import org.fast.develop.logicflow.runtime.monitor.ExecuteLog;
import org.fast.develop.logicflow.runtime.monitor.LogicFlowMonitor;
import org.fast.develop.logicflow.runtime.node.LineNode;
import org.fast.develop.logicflow.runtime.node.LogicFlowNode;
import org.fast.develop.logicflow.runtime.ws.LogicFlowEventPublisher;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author czh
 * @version V1.0
 * @data Created on 2023-01-04 16:56
 */
@Slf4j
public class InstanceExecutor {


    private DefaultJuelExpressionActuator expressionActuator;

    private TaskExecutor taskExecutor;

    private LogicFlowContextManager logicFlowContextManager;

    private IThreadStore threadStore;

    public InstanceExecutor(DefaultJuelExpressionActuator expressionActuator, TaskExecutor taskExecutor, LogicFlowContextManager logicFlowContextManager, IThreadStore threadStore) {
        this.expressionActuator = expressionActuator;
        this.taskExecutor = taskExecutor;
        this.logicFlowContextManager = logicFlowContextManager;
        this.threadStore = threadStore;
    }

    @SneakyThrows
    public void execute(LogicFlowInstance logicFlowInstance, Map variables, LogicFlowContext logicFlowContext) {
        Exception exception = null;
        try {
            logicFlowInstance.setVariables(variables);
            executeNode(logicFlowInstance.getLogicFlowNode(), logicFlowInstance, variables, logicFlowContext);
            logicFlowInstance.setLogicFlowMonitor(logicFlowContextManager.get().getLogicFlowMonitor());

        } catch (Exception e) {
            exception = e;
            LogicFlowMonitor logicFlowMonitor;
            if (Objects.isNull(logicFlowInstance.getLogicFlowMonitor())) {
                logicFlowMonitor = new LogicFlowMonitor();
            } else {
                logicFlowMonitor = logicFlowInstance.getLogicFlowMonitor();
            }
            logicFlowMonitor.setExceptionMsg(e.getMessage());
            logicFlowInstance.setLogicFlowMonitor(logicFlowMonitor);
            e.printStackTrace();
            throw new LogicFlowRuntimeException(e.getMessage(), logicFlowContextManager.get().getLogicFlowMonitor());
        } finally {
            logicFlowContextManager.remove();
            // 执行结束触发事件
            ExecuteDebugConfig executeDebugConfig = logicFlowContext.getExecuteDebugConfig();
            if (Objects.nonNull(executeDebugConfig) && StringUtils.isNotBlank(executeDebugConfig.getSessionId())) {
                String sessionId = executeDebugConfig.getSessionId();
                LogicFlowEventPublisher.sendMsg(Objects.isNull(exception) ? WebsocketStatusEnum.STATUS_201 : WebsocketStatusEnum.STATUS_500, sessionId, logicFlowInstance, null);
                LogicFlowEventPublisher.close(sessionId);
            }
        }
    }


    @SneakyThrows
    private void executeNode(LogicFlowNode logicFlowNode, LogicFlowInstance logicFlowInstance, Map variables, LogicFlowContext logicFlowContext) {
        //获取debug配置信息
        ExecuteDebugConfig executeDebugConfig = logicFlowContext.getExecuteDebugConfig();
        String sessionId = executeDebugConfig.getSessionId();
        ThreadLog threadLog = null;
        //执行前获取最新配置信息
        if (!StringUtils.isEmpty(sessionId)) {
            threadLog = threadStore.get(sessionId);
            if (Objects.nonNull(threadLog)) {
                executeDebugConfig = threadLog.getExecuteDebugConfig();
            } else {
                throw new LogicFlowException("获取debug配置信息失败");
            }
        }
        // debug验证
        this.debugCheck(sessionId, executeDebugConfig, threadLog, logicFlowInstance, logicFlowNode);
        // 记录日志
        if(!(logicFlowNode.getElement() instanceof SequenceFlow)) {
            this.setExecuteLog(logicFlowNode.getElement().getName(), logicFlowNode.getElement(), logicFlowContext);
        }
        if (log.isDebugEnabled()) {
            log.debug("执行节点：name：{}， element：{}", logicFlowNode.getElement().getName(), logicFlowNode.getElement());
        }
        // 更新日志
        logicFlowContextManager.set(logicFlowContext);
        logicFlowInstance.setLogicFlowMonitor(logicFlowContext.getLogicFlowMonitor());
        if (logicFlowNode.getElement() instanceof IfElseControl) {
            logicFlowNode.setLogicFlowInstance(logicFlowInstance);
            ifelseExecute(logicFlowNode, logicFlowInstance, variables, logicFlowContext);
        } else {
            //执行任务
            logicFlowNode.setLogicFlowInstance(logicFlowInstance);
            taskExecutor.execute(logicFlowNode);

            this.debugUpdate(sessionId, threadLog, logicFlowNode.getElement());

            //往下执行
            logicFlowNode.getNextNodes().forEach(nextNodeLogicFlowNode -> {
                nextNodeLogicFlowNode.setLogicFlowInstance(logicFlowInstance);
                executeNode(nextNodeLogicFlowNode, logicFlowInstance, variables, logicFlowContext);
            });
        }
    }

    private void ifelseExecute(LogicFlowNode ifelseNode, LogicFlowInstance logicFlowInstance, Map variables, LogicFlowContext logicFlowContext) {
        Element element = ifelseNode.getElement();
        if (element instanceof IfElseControl) {
            List<LogicFlowNode> logicFlowNodes = new ArrayList();
            List<LogicFlowNode> defaultNodes = new ArrayList<>();
            AtomicReference<Boolean> result = new AtomicReference<>(false);
            ifelseNode.getNextNodes().forEach(sequenceFlowNode -> {
                if (!result.get() && sequenceFlowNode instanceof LineNode) {
                    LineNode lineNode = (LineNode) sequenceFlowNode;
                    if (lineNode.getDefault()) {
                        defaultNodes.add(lineNode);
                    }
                    ConditionExpression conditionExpression = lineNode.getExpression();
                    if (conditionExpression != null) {
                        try {
                            //执行条件表达式
                            Boolean aBoolean = executeConditionExpression(conditionExpression, variables);
                            result.set(aBoolean);
                        } catch (Exception e) {
                            result.set(false);
                            e.printStackTrace();
                        }
                        if (result.get()) {
                            logicFlowNodes.add(sequenceFlowNode);
                        }
                    } else {
                        logicFlowNodes.add(sequenceFlowNode);
                    }
                }
            });

            if (logicFlowNodes.size() == 0 && defaultNodes.size() == 0) {
                throw new LogicFlowException(StrUtil.format("当前控制节点ifelse：{} 没有匹配到可执行线！", ifelseNode.getElement().getName()));
            }

            if (logicFlowNodes.size() > 0) {
                logicFlowNodes.forEach(logicFlowNode -> {
                    executeNode(logicFlowNode, logicFlowInstance, variables, logicFlowContext);
                });
            } else {
                executeNode(defaultNodes.get(0), logicFlowInstance, variables, logicFlowContext);
            }
        }
    }


    private Boolean executeConditionExpression(ConditionExpression conditionExpression, Map<String, Object> variables) {
        return expressionActuator.invokeJuelExpressionResultBoolean(conditionExpression.getExpression(), variables);
    }

    private void setExecuteLog(String name, Resource resource, LogicFlowContext logicFlowContext) {
        ExecuteLog executeLog = new ExecuteLog(name, resource);
        logicFlowContext.getLogicFlowMonitor().getExecuteIds().add(resource.getResourceId());
        logicFlowContext.getLogicFlowMonitor().getExecuteLogs().add(executeLog);
    }

    @SneakyThrows
    private void debugCheck(String sessionId,
                            ExecuteDebugConfig executeDebugConfig,
                            ThreadLog threadLog,
                            LogicFlowInstance logicFlowInstance,
                            LogicFlowNode logicFlowNode) {
        if (StringUtils.isNotBlank(sessionId) && executeDebugConfig.getIsOpen()) {
            // 单步
            if (ExecuteDebugEnum.step_over.equals(executeDebugConfig.getType())) {
                if (executeDebugConfig.getStepOverFlag()) {
                    this.threadWait(sessionId, threadLog, logicFlowInstance, logicFlowNode.getElement().getResourceId());
                }
            } else if (ExecuteDebugEnum.start.equals(executeDebugConfig.getType()) || ExecuteDebugEnum.resume_program.equals(executeDebugConfig.getType())) {
                // 断点
                List<String> debugResourceIds = executeDebugConfig.getDebugResourceIds();
                if (Objects.isNull(debugResourceIds) || debugResourceIds.size() == 0) {
                } else {
                    if (debugResourceIds.contains(logicFlowNode.getElement().getResourceId())) {
                        this.threadWait(sessionId, threadLog, logicFlowInstance, logicFlowNode.getElement().getResourceId());
                    }
                }
            }
        }
    }

    private void debugUpdate(String sessionId, ThreadLog threadLog, Element element) {
        if (Objects.nonNull(threadLog)) {
            if (element instanceof SequenceFlow) {
                threadLog.getExecuteDebugConfig().setStepOverFlag(true);
            }
            this.threadStore.put(sessionId, threadLog);
        }
    }

    @SneakyThrows
    private void threadWait(String sessionId, ThreadLog threadLog, LogicFlowInstance logicFlowInstance, String activeId) {
        synchronized (threadLog.getThread()) {
            threadLog.setDate(new Date());
            threadLog.setLogicFlowInstance(logicFlowInstance);
            threadStore.put(sessionId, threadLog);
            LogicFlowEventPublisher.sendMsg(WebsocketStatusEnum.STATUS_200, sessionId, logicFlowInstance, activeId);
            threadLog.getThread().wait();
        }
    }
}
